/*
	Copyright (C) 2011 Salil Bhagurkar

	This file is part of illusion

	illusion is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	illusion is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with illusion.
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <klib/lib.h>
#include <kernel/module.h>
#include <kernel/ilapp.h>
#include <kernel/errors.h>
#include <apimod/apimod.h>
#include <kernel/vfs.h>
#include <kernel/input.h>
#include <kernel/list.h>
#include <kernel/ilapp_base.h>
#include <kernel/types.h>

#include "ishell.h"

struct apimod_console *console;
struct apimod_base *base;
struct apimod_lib_string *lib_string;
struct apimod_lib_memory *memory;
struct apimod_fs *fs;
struct apimod_fs1 *fs1;
struct apimod_ini *ini;

#define BUF_LEN 512

char cmdbuf[BUF_LEN];
static char cmd[BUF_LEN], args[BUF_LEN];

static void get_command(char *cmd, char *args, char *cmdbuf)
{
	unsigned j = 0;
	char *buf_i = cmdbuf;
	while((*buf_i) != '\0') {
		if((*buf_i) == ' ') {
			break;
		}
		cmd[j++] = *buf_i++;
	}
	cmd[j] = '\0';
	j = 0;
	//Skip over the space
	if((*buf_i) != '\0')
		buf_i++;
	while((*buf_i) != '\0') {
		args[j++] = *buf_i++;
	}
	args[j] = '\0';
}

struct ini_config *conf;
uint_t ver, date;
uint_t hlp_len;
char *hlp_text;

static int start_exe(char **output, uint_t *out_len, char *lookup_path, bool redirect)
{
	unsigned long fd, fd_child;
	char *_cmd = cmd;
	int err = 0;

	//Add a ".exe" if not already present
	if(!lib_string->ends_with(cmd, ".exe")) {
		_cmd = (char *)memory->malloc(BUF_LEN);
		lib_string->snprintf(_cmd, BUF_LEN, "%s.exe", cmd);
	}

	//Create the full path
	char path[BUF_LEN];
	lib_string->copy(path, lookup_path, BUF_LEN);
	lib_string->cat(path, BUF_LEN, "/");
	lib_string->cat(path, BUF_LEN, _cmd);

	//Create the process
	char *_args = lib_string->length(args) > 0 ? args : null;
	unsigned long pid;
	base->try();
	err = base->create_process(&pid, path, _args);
	base->exit_try();
	if(err) {
		console->printf("Could not locate executable: %s\n", path);
		goto error_return;
	}

	char rbuf[15];

	uint_t redirect_fid, this_fd;

	//If we need to redirect the output, create a file for the process
	if(redirect) {
		base->get_current(&this_fd);
		fs->change_state(this_fd, STATE_WRITE);
		lib_string->snprintf(rbuf, 15, "out-%u", pid);
		fs->create(this_fd, rbuf);
		fs->close(this_fd);

		fs->open(rbuf, STATE_WRITE, &redirect_fid);
		base->redirect_out(pid, redirect_fid);
	}

	//Resume the created process and wait for it to complete
	base->resume_process(pid);
	base->wait_for_process(pid);
	base->kill_process(pid); //Terminate the zombie

	//Collect the output
	if(redirect) {

		fs->close(redirect_fid);

		char *out_buf = fs1->read_all_file(out_len, rbuf);
		*output = out_buf;

		//Delete the file we created
		base->get_current(&this_fd);
		fs->change_state(this_fd, STATE_WRITE);
		fs->open(rbuf, STATE_WRITE, &redirect_fid);
		fs->delete(this_fd, redirect_fid);
		fs->close(this_fd);
	}

error_return:
common_return:

	//Free if we allocated in this function
	if(_cmd != cmd) {
		memory->free(_cmd, BUF_LEN);
		_cmd = null;
	}

	return err;
}

static void exec_cmd(char **output, uint_t *out_len, char *cmd, char *args, bool redirect)
{
	*output = "";
	if(lib_string->equals(cmd, "help")) {
		console->printf("Illusion Shell. Build %d\n", ver);
		console->printf("Build Date %u\n", date);
		console->print(hlp_text, hlp_len);
	} else if(lib_string->equals(cmd, "exit")) {
		base->exit_process();
	} else if(lib_string->equals(cmd, "cd")) {
		unsigned long fd;
		base->try();
		if(fs->open(args, STATE_READ, &fd)) {
			console->printf("Could not find: %s\n", args);
			base->exit_try();
			return;
		}
		base->exit_try();

		//Change the working directory
		base->set_current(fd);
		fs->close(fd);
	} else if(lib_string->equals(cmd, "src")) {

	} else {
		start_exe(output, out_len, "illusion:/rdisk", redirect);
	}
}


static int is_number(char *sym)
{
	if(sym[0] >= '0' && sym[0] <= '9')
		return 1;
	return 0;
}

unsigned get_number(char *sym)
{
	if(sym[0] == '0' && sym[1] == 'x')
		return lib_string->hex_string(sym);
	return lib_string->dec_string(sym);
}

struct shell_var {
	char name[SYM_NAME_LEN];
	char value[SYM_NAME_LEN];
	struct shell_var *next, *prev;
};

static struct shell_var *vars, *vars_tail;

static struct shell_var *get_shell_var(char *name)
{
	struct shell_var *var;
	list_for(vars, var) {
		if(lib_string->equals(var->name, name))
			return var;
	}
	return null;
}

static void write_shell_var(char *name, char *value)
{
	struct shell_var *n;
	n = get_shell_var(name);
	if(!n) {
		n = (struct shell_var *)memory->malloc(sizeof(struct shell_var));
		lib_string->copy(n->name, name, SYM_NAME_LEN);
		list_attach(vars, vars_tail, n);
	}
	lib_string->copy(n->value, value, SYM_NAME_LEN);
}


extern void parse_expression();

static int result_silence;

void shell_exec()
{
	if(!result_silence) {
		uint_t i;
		char *in_buf = (char *)memory->malloc(BUF_LEN);
		char *in_buf_orig = in_buf;
		lib_string->copy(in_buf, cmdbuf, BUF_LEN);
		uint_t cmdbuf_i = 0;
		//Skip over spaces at the start
		while(((*in_buf) == ' ' || (*in_buf) == '\t') && (*in_buf) != '\0') {
			in_buf++;
		}
		for(i = 0; i < BUF_LEN; ) {
			if(in_buf[i] == '\0')
				break;
			if(in_buf[i] == '$') {
				char sym_name[SYM_NAME_LEN];
				uint_t sym_i = 1;
				sym_name[0] = '$';
				i++;
				while((in_buf[i] >= 'a' && in_buf[i] <= 'z')
					|| (in_buf[i] >= 'A' && in_buf[i] <= 'Z')
					|| (in_buf[i] >= '0' && in_buf[i] <= '9')
					|| (in_buf[i] == '_')) {
					sym_name[sym_i++] = in_buf[i++];
				}
				sym_name[sym_i] = '\0';
				struct shell_var *var = get_shell_var(sym_name);
				if(!var) {
					console->printf("ishell: No such variable: %s\n", sym_name);
					return;
				}
				char *sym_val = var->value;
				while((*sym_val) != '\0' && cmdbuf_i < (BUF_LEN - 2)) {
					cmdbuf[cmdbuf_i++] = *sym_val;
					sym_val++;
				}
				cmdbuf[cmdbuf_i] = '\0';
			} else {
				if(cmdbuf_i < (BUF_LEN - 2)) {
					cmdbuf[cmdbuf_i++] = in_buf[i++];
					cmdbuf[cmdbuf_i] = '\0';
				}
			}
		}
		memory->free(in_buf_orig, BUF_LEN);
		//Check if the command is a variable print
		if(in_buf[0] == '$') {
			console->printf(cmdbuf);
		} else {
			get_command(cmd, args, cmdbuf);
			char *output;
			uint_t out_len;
			exec_cmd(&output, &out_len, cmd, args, false);
		}
	}
}

static char sym1_buf[BUF_LEN], sym2_buf[BUF_LEN];

int shell_op(char *result, char *op, char *sym1, char *sym2)
{
	lib_string->copy(sym1_buf, sym1, BUF_LEN);
	lib_string->copy(sym2_buf, sym2, BUF_LEN);
	if(sym1[0] == '$') {
		struct shell_var *sym1_var;
		sym1_var = get_shell_var(sym1);
		if(!sym1_var) {
			console->printf("ishell: No such variable: %s\n", sym1);
			return EPARAM;
		}
		if(sym1_var)
			lib_string->copy(sym1_buf, sym1_var->value, SYM_NAME_LEN);
	} else if(sym1[0] == '`') {
		//Remove the '`'
		sym1++;
		sym1[lib_string->length(sym1) - 1] = '\0';
		//Execute a command
		get_command(cmd, args, sym1);
		char *output;
		uint_t len;
		exec_cmd(&output, &len, cmd, args, true);
		if(len > 0) {
			memory->copy(sym1_buf, output, len);
			sym1_buf[len] = '\0';
			memory->free(output, len);
		}
	}
	if(sym2[0] == '$') {
		struct shell_var *sym2_var;
		sym2_var = get_shell_var(sym2);
		//Allow assignment
		if(!sym2_var && op[0] != '=') {
			console->printf("ishell: No such variable: %s\n", sym2);
			return EPARAM;
		}
		if(sym2_var) {
			//Allow reassignment
			if(op[0] != '=')
				lib_string->copy(sym2_buf, sym2_var->value, SYM_NAME_LEN);
		}
	} else if(sym2[0] == '`') {
		sym2++;
		sym2[lib_string->length(sym2) - 1] = '\0';
		get_command(cmd, args, sym2);
		char *output;
		uint_t len;
		exec_cmd(&output, &len, cmd, args, true);
		if(len > 0) {
			memory->copy(sym2_buf, output, len);
			sym2_buf[len] = '\0';
			memory->free(output, len);
		}
	}
	sym1 = sym1_buf;
	sym2 = sym2_buf;
	console->printf("sym1 = %s, sym2 = %s\n", sym1, sym2);
	switch(op[0]) {
	case '+':
		if(is_number(sym1) && is_number(sym2)) {
			unsigned num1 = get_number(sym1);
			unsigned num2 = get_number(sym2);
			lib_string->snprintf(result, SYM_NAME_LEN, "%u", num1 + num2);
		} else {
			lib_string->snprintf(result, SYM_NAME_LEN, "%s%s", sym1, sym2);
		}
		return 0;
	case '-':
		if(is_number(sym1) && is_number(sym2)) {
			unsigned num1 = get_number(sym1);
			unsigned num2 = get_number(sym2);
			lib_string->snprintf(result, SYM_NAME_LEN, "%u", num2 - num1);
		} else {
			console->printf("ishell: Cannot subtract %s and %s\n", sym1, sym2);
			return EPARAM;
		}
		return 0;
	case '*':
		if(is_number(sym1) && is_number(sym2)) {
			unsigned num1 = get_number(sym1);
			unsigned num2 = get_number(sym2);
			lib_string->snprintf(result, SYM_NAME_LEN, "%u", num1 * num2);
		} else {
			console->printf("ishell: Cannot multiply %s and %s\n", sym1, sym2);
			return EPARAM;
		}
		return 0;
	case '/':
		if(is_number(sym1) && is_number(sym2)) {
			unsigned num1 = get_number(sym1);
			unsigned num2 = get_number(sym2);
			lib_string->snprintf(result, SYM_NAME_LEN, "%u", num1 / num2);
		} else {
			console->printf("ishell: Cannot divide %s and %s\n", sym1, sym2);
			return EPARAM;
		}
		return 0;
	case '=':
		result_silence = 1;
		if(sym2[0] != '$') {
			console->printf("ishell: Invalid assignment to: %s\n", sym2);
			return EPARAM;
		}
		write_shell_var(sym2, sym1);
		return 0;
	default:
		console->printf("ishell: Invalid operation: %s\n", op);
		break;
	}
	lib_string->copy(result, "", SYM_NAME_LEN);
	return 0;
}

static void ishell_parse()
{
	result_silence = 0;
	parse_expression();
}

int ilapp_main()
{
	GETI("console", &console);
	GETI("base", &base);
	GETI("lib_string", &lib_string);
	GETI("memory", &memory);
	GETI("fs", &fs);
	GETI("fs1", &fs1);
	GETI("ini", &ini);

	uint_t this_fd;

	base->get_version(&ver, &date);

	//Read the configuration
	ini->read(&conf, "../ishell_config.ini");

	hlp_text = fs1->read_all_file(&hlp_len, "../ishell_help.txt");

//	lib_string->copy(cmdbuf, "ishell", BUF_LEN);
//	ishell_parse();

	while(1) {
		//Open the working directory
		struct sys_info1 info1;
		base->get_current(&this_fd);
		fs->get_info1(this_fd, &info1);
		fs->close(this_fd);

		console->printf("\n[%s]:ishell> ", info1.path);
		console->read_line(cmdbuf, BUF_LEN);
		if(cmdbuf[0] == '\0')
			continue;
		ishell_parse();
	}
	return 0;
}
